/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: Kernel exposed object to user
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2022-4-30      JasonHu           Init
 */

#include <base/exobj.h>
#include <base/malloc.h>
#include <base/debug.h>
#define NX_LOG_LEVEL NX_LOG_ERROR
#include <base/log.h>
#include <base/thread.h>

NX_PRIVATE const char * exobj_name_table[NX_EXOBJ_TYPE_NR] = {
    "None",
    "thread",
    "snapshot",
    "mutex",
    "semaphore",
    "device",
    "timer",
    "udriver",
    "hub",
    "shm",
    "shmaddr",
    "file",
    "dir",
    "ipc",
    "ipc_channel",
    "fifo",    
};

const char * NX_ExposedObjectGetNameByType(NX_ExposedObjectType type)
{
    if (type >= NX_EXOBJ_TYPE_NR)
    {
        return exobj_name_table[0];
    }
    return exobj_name_table[type];
}

NX_Error NX_ExposedObjectTableInit(NX_ExposedObjectTable * table)
{
    int i;

    if (!table)
    {
        return NX_EINVAL;
    }
    table->maxCount = 0;
    NX_SpinInit(&table->lock);
    for (i = 0; i < NX_EXOBJ_DEFAULT_NR; i++)
    {
        table->objects[i] = NX_NULL;
    }
    return NX_EOK;
}

/**
 * exit object table no need free table, because just exit table, not destroy table
 */
NX_Error NX_ExposedObjectTableExit(NX_ExposedObjectTable * table)
{
    NX_Solt solt;
    NX_Error err;

    if (!table)
    {
        return NX_EINVAL;
    }

    /* uninstall all objects */
    for (solt = table->maxCount - 1; solt >= 0; solt--)
    {
        err = NX_ExposedObjectUninstalll(table, solt);
        if (err != NX_EOK)
        {
            NX_LOG_E("exit table faield! %d->%s", solt, NX_ErrorToString(err));
        }
    }

    return NX_EOK;
}

NX_Error NX_ExposedObjectTableDump(NX_ExposedObjectTable * table)
{
    int i;
    NX_ExposedObject * object;

    if (!table)
    {
        return NX_EINVAL;
    }
    for (i = 0; i < table->maxCount; i++)
    {
        object = table->objects[i];
        if (object)
        {
            NX_LOG_D("exobj table[%d] object=%p, type=%s, close handler:%p", i, object->object,
                NX_ExposedObjectGetNameByType(object->type), object->close);
        }
    }
    return NX_EOK;
}

NX_ExposedObject * NX_ExposedObjectCreate(void)
{
    NX_ExposedObject * exobj;
    exobj = NX_MemAlloc(sizeof(NX_ExposedObject));
    if (!exobj)
    {
        return NX_NULL;
    }
    exobj->type = NX_EXOBJ_NONE;
    exobj->close = NX_NULL;
    exobj->object = NX_NULL;
    NX_AtomicSet(&exobj->reference, 0);
    return exobj;
}

NX_Error NX_ExposedObjectDestroy(NX_ExposedObject * exobj)
{
    if (!exobj)
    {
        return NX_EINVAL;
    }
    exobj->type = NX_EXOBJ_NONE;
    exobj->close = NX_NULL;
    exobj->object = NX_NULL;
    NX_AtomicSet(&exobj->reference, 0);

    NX_MemFree(exobj);
    return NX_EOK;
}

NX_Solt NX_ExposedObjectGetFreeSolt(NX_ExposedObjectTable * table)
{
    NX_ASSERT(table);
    
    NX_Solt i;

    for (i = 0; i < NX_EXOBJ_DEFAULT_NR; i++)
    {
        if (table->objects[i] == NX_NULL)
        {
            return i;
        }
    }
    return -1;
}

NX_PRIVATE void NX_ExposedObjectRefAndBind(NX_ExposedObjectTable * table, NX_Solt solt, NX_ExposedObject * exobj)
{
    NX_AtomicInc(&exobj->reference);

    table->objects[solt] = exobj;
    if (solt >= table->maxCount)
    {
        table->maxCount++;
    }
}

NX_Solt NX_ExposedObjectAlloc(NX_ExposedObjectTable * table)
{
    NX_ASSERT(table);
    
    NX_Solt i;
    NX_ExposedObject * object;

    i = NX_ExposedObjectGetFreeSolt(table);
    if (i < 0)
    {
        return -1;
    }

    object = NX_ExposedObjectCreate();
    if (!object)
    {
        return -1;
    }

    NX_ExposedObjectRefAndBind(table, i, object);

    return i;

}

NX_Error NX_ExposedObjectFree(NX_ExposedObjectTable * table, NX_ExposedObject * exobj)
{
    NX_ASSERT(table);
    
    NX_Solt i;

    for (i = 0; i < table->maxCount; i++)
    {
        if (table->objects[i] == exobj)
        {
            if (NX_AtomicGet(&exobj->reference) <= 0)
            {
                NX_ExposedObjectDestroy(exobj);
            }
            table->objects[i] = NX_NULL;
            return NX_EOK;
        }
    }
    return NX_ENORES;
}

NX_ExposedObject * NX_ExposedObjectGet(NX_ExposedObjectTable * table, NX_Solt solt)
{
    if (!table)
    {
        return NX_NULL;
    }

    if (solt < table->maxCount)
    {
        return table->objects[solt];
    }

    return NX_NULL;
}

NX_Solt NX_ExposedObjectLocate(NX_ExposedObjectTable * table, void * object, NX_ExposedObjectType type)
{
    NX_Solt solt;
    NX_UArch level;
    NX_ExposedObject * exobj = NX_NULL;

    if (!table || !object)
    {
        return NX_SOLT_INVALID_VALUE;
    }

    if (NX_SpinLockIRQ(&table->lock, &level) != NX_EOK)
    {
        return NX_SOLT_INVALID_VALUE;
    }

    for (solt = 0; solt < table->maxCount; solt++)
    {
        exobj = table->objects[solt];

        if (exobj != NX_NULL && exobj->object == object && exobj->type == type)
        {
            NX_SpinUnlockIRQ(&table->lock, level);
            return solt;
        }
    }
    NX_SpinUnlockIRQ(&table->lock, level);
    return NX_SOLT_INVALID_VALUE;
}

NX_Error NX_ExposedObjectInstall(NX_ExposedObjectTable * table, void * object, NX_ExposedObjectType type, NX_SoltCloseHandler handler, NX_Solt * outSolt)
{
    NX_Solt solt;
    NX_ExposedObject * exobj = NX_NULL;
    NX_UArch level;

    if (!table)
    {
        return NX_EINVAL;
    }

    if (NX_SpinLockIRQ(&table->lock, &level) != NX_EOK)
    {
        return NX_EINTR;
    }

    solt = NX_ExposedObjectAlloc(table);
    if (solt < 0)
    {
        NX_SpinUnlockIRQ(&table->lock, level);
        return NX_ENORES;
    }

    exobj = table->objects[solt];

    exobj->object = object;
    exobj->type = type;
    exobj->close = handler;

    if (outSolt)
    {
        *outSolt = solt;
    }

    NX_LOG_D("install exobj: task=%d solt=%d object=%p/%s", 
        NX_ThreadSelf()->tid, solt, exobj->object, NX_ExposedObjectGetNameByType(exobj->type));

    NX_SpinUnlockIRQ(&table->lock, level);
    return NX_EOK;
}

NX_Error NX_ExposedObjectUninstalll(NX_ExposedObjectTable * table, NX_Solt solt)
{
    NX_ExposedObject * exobj = NX_NULL;
    NX_UArch level;
    NX_Error err;

    if (!table)
    {
        return NX_EINVAL;
    }
    
    if (solt >= table->maxCount)
    {
        return NX_EINVAL;
    }

    exobj = table->objects[solt];

    if (!exobj)
    {
        return NX_EOK; /* no need uninstall */
    }

    NX_LOG_D("uninstall exobj: task=%d solt=%d object=%p/%s",
        NX_ThreadSelf()->tid, solt, exobj->object, NX_ExposedObjectGetNameByType(exobj->type));

    NX_AtomicDec(&exobj->reference);

    if (NX_AtomicGet(&exobj->reference) <= 0 && exobj->close)
    {
        err = exobj->close(exobj->object, exobj->type);
        if (err != NX_EOK)
        {
            NX_LOG_E("do close solt %d error %s, object type:%s", solt,
                NX_ErrorToString(err),NX_ExposedObjectGetNameByType(exobj->type));
        }
    }

    NX_Thread * self = NX_ThreadSelf();
    self->flags |= NX_THREAD_FLAG_UNINTERRUPTABLE;
    NX_SpinLockIRQ(&table->lock, &level);
    self->flags &= ~NX_THREAD_FLAG_UNINTERRUPTABLE;

    /* destroy exobj */
    NX_ExposedObjectFree(table, exobj);

    NX_SpinUnlockIRQ(&table->lock, level);

    return NX_EOK;
}

NX_Error NX_ExposedObjectCopy(NX_ExposedObjectTable * dst, NX_ExposedObjectTable * src, NX_Solt solt, NX_Solt * outSolt)
{
    NX_ExposedObject * exobj;
    NX_Solt newSolt;
    NX_UArch level;

    if (!dst || !src)
    {
        return NX_EINVAL;
    }

    exobj = NX_ExposedObjectGet(src, solt);
    if (!exobj)
    {
        return NX_EFAULT;
    }
    
    NX_SpinLockIRQ(&dst->lock, &level);
    /* alloc exobj  */
    newSolt = NX_ExposedObjectGetFreeSolt(dst);
    if (newSolt < 0)
    {
        NX_SpinUnlockIRQ(&dst->lock, level);
        return NX_ENORES;
    }

    NX_ExposedObjectRefAndBind(dst, newSolt, exobj);
 
    if (outSolt)
    {
        *outSolt = newSolt;
    }

    NX_SpinUnlockIRQ(&dst->lock, level);
    return NX_EOK;
}

NX_Error NX_ExposedObjectDuplicate(NX_ExposedObjectTable * table, NX_Solt solt, NX_Solt * outSolt)
{
    NX_ExposedObject * exobj;
    NX_Solt newSolt;
    NX_UArch level;

    if (!table)
    {
        return NX_EINVAL;
    }

    exobj = NX_ExposedObjectGet(table, solt);
    if (!exobj)
    {
        return NX_EFAULT;
    }
    
    NX_SpinLockIRQ(&table->lock, &level);
    /* alloc exobj  */
    newSolt = NX_ExposedObjectGetFreeSolt(table);
    if (newSolt < 0)
    {
        NX_SpinUnlockIRQ(&table->lock, level);
        return NX_ENORES;
    }

    NX_ExposedObjectRefAndBind(table, newSolt, exobj);

    if (outSolt)
    {
        *outSolt = newSolt;
    }
 
    NX_SpinUnlockIRQ(&table->lock, level);
    return NX_EOK;
}

NX_Error NX_ExposedObjectDuplicateTo(NX_ExposedObjectTable * table, NX_Solt destSolt, NX_Solt srcSolt)
{
    NX_ExposedObject * srcExobj;
    NX_UArch level;
    NX_Error err;

    if (!table)
    {
        return NX_EINVAL;
    }

    /* get source */
    srcExobj = NX_ExposedObjectGet(table, srcSolt);
    if (!srcExobj)
    {
        return NX_EFAULT;
    }
    
    /* get dest */
    if (NX_ExposedObjectGet(table, destSolt) != NX_NULL)
    {
        /* close dest if exist */
        err = NX_ExposedObjectUninstalll(table, destSolt);
        if (err != NX_EOK)
        {
            return err;
        }
    }
    
    NX_SpinLockIRQ(&table->lock, &level);

    NX_ExposedObjectRefAndBind(table, destSolt, srcExobj);

    NX_SpinUnlockIRQ(&table->lock, level);
    return NX_EOK;
}
